--dispatch wndproc messages to individual control handlers.
--just set router.proc to wndproc of all windows receiving interesting
--messages, then add handlers: each handler will be called whenever a message
--of the specified class involving the specified control will be received.
setfenv(1, require'winapi')
require'winapi.wm' --needs nothing from wm but loads it for convenience of clients

Router = {}
setmetatable(Router, Router)

Router.decoders = {} --{class = decoder}; class property

--decoder = function(window, WM, wParam, lParam) -> target_control, notification arguments...
function Router:set_decoder(class, decoder)
	self.decoders[class] = decoder
end

local function autocreate(t, k)
	local dt = t[k]
	if not dt then dt = {}; t[k] = dt end
	return dt
end

--handler = function(notification arguments...)
function Router:add_handler(class, control, handler)
	if not handler then control, handler = nil, control end --control is optional!
	assert2(self.decoders[class], 'unknown message class "%s"', class)
	local t = autocreate(self.handlers, class)
	t = autocreate(t, control and tostring(control) or 'all')
	t[handler] = true
end

function Router:remove_handler(class, control, handler)
	if not handler then control, handler = nil, control end --control is optional!
	assert2(self.decoders[class], 'unknown message class "%s"', class)
	self.handlers[class][control and tostring(control) or 'all'][handler] = nil
end

function Router:remove_control(control)
	for _, ht in pairs(self.handlers) do
		ht[tostring(control)] = nil
	end
end

local function handle(handlers, control, ...)
	if not control then return end --the decoder didn't decode the message
	local ht = handlers[tostring(control)]
	if ht then
		for handler in pairs(ht) do
			local ret = handler(control, ...)
			if ret ~= nil then return ret end
		end
	end
	local ht = handlers.all
	if ht then
		for handler in pairs(ht) do
			local ret = handler(control, ...)
			if ret ~= nil then return ret end
		end
	end
end
function Router:dispatch(...)
	for class, handlers in pairs(self.handlers) do
		local decoder = self.decoders[class]
		local ret = handle(handlers, decoder(...))
		if ret ~= nil then return ret end
	end
	return self.default_handler(...)
end

function Router:__call()
	local o = setmetatable({}, {__index = self})
	o.handlers = {} --{class = {tostring(control) = handler}}
	o.proc = ffi.cast('WNDPROC', function(...) return o:dispatch(...) end)
	return o
end

--showcase

if not ... then
local function test_decoder(window, WM, wParam, lParam)
	return window, WM, lParam, wParam
end
Router:set_decoder('Test', test_decoder)
local router = Router()
local proc = router.proc
local function test_handler1(window, WM, lParam, wParam)
	print('control handler1 called', window, WM, lParam, wParam)
end
local function test_handler2(window, WM, lParam, wParam)
	print('control handler2 called', window, WM, lParam, wParam)
end
local function test_handler3(window, WM, lParam, wParam)
	print('class handler3 called', window, WM, lParam, wParam)
end
local window = ffi.cast('HWND', nil)
router.default_handler = function() print'default handler called'; return 0 end
router:add_handler('Test', window, test_handler1)
router:add_handler('Test', window, test_handler2)
router:add_handler('Test', test_handler3)
proc(window, 11, 22, 33)
router:remove_control(window)
proc(window, 11, 22, 33)
router:remove_handler('Test', test_handler3)
proc(window, 11, 22, 33)
end

